﻿namespace Edge.Utilities.Linq
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web;
    using System.Web.Caching;

    /// <summary>
    /// Enables the partial evalutation of queries.
    /// From http://msdn.microsoft.com/en-us/library/bb546158.aspx
    /// </summary>
    public static class Evaluator
    {
        /// <summary>
        /// Performs evaluation and replacement of independent sub-trees
        /// </summary>
        /// <param name="expression">The root of the expression tree.</param>
        /// <param name="canBeEvaluated">A function that decides whether a given expression node can be part of the local function.</param>
        /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
        public static Expression PartialEval(Expression expression, Func<Expression, bool> canBeEvaluated)
        {
            return new SubtreeEvaluator(new Nominator(canBeEvaluated).Nominate(expression)).Eval(expression);
        }

        /// <summary>
        /// Performs evaluation and replacement of independent sub-trees
        /// </summary>
        /// <param name="expression">The root of the expression tree.</param>
        /// <returns>A new tree with sub-trees evaluated and replaced.</returns>
        public static Expression PartialEval(Expression expression)
        {
            return PartialEval(expression, Evaluator.CanBeEvaluatedLocally);
        }

        /// <summary>
        /// Determines if an expression can be evaluated locally.
        /// </summary>
        /// <param name="expression">Expression to inspect.</param>
        /// <returns>Result of the inspection.</returns>
        private static bool CanBeEvaluatedLocally(Expression expression)
        {
            return expression.NodeType != ExpressionType.Parameter;
        }

        /// <summary>
        /// Evaluates and replaces sub-trees when first candidate is reached (top-down).
        /// </summary>
        private class SubtreeEvaluator : ExpressionVisitor
        {
            /// <summary>
            /// Expression candidates.
            /// </summary>
            private HashSet<Expression> candidates;

            /// <summary>
            /// Initializes a new instance of the SubtreeEvaluator class.
            /// </summary>
            /// <param name="candidates">Expression candidates.</param>
            internal SubtreeEvaluator(HashSet<Expression> candidates)
            {
                this.candidates = candidates;
            }

            /// <summary>
            /// Evaluates an expression.
            /// </summary>
            /// <param name="exp">Expression to evaluate.</param>
            /// <returns>Resulting expression.</returns>
            internal Expression Eval(Expression exp)
            {
                return this.Visit(exp);
            }

            /// <summary>
            /// Visits an expression.
            /// </summary>
            /// <param name="exp">Expression to visit.</param>
            /// <returns>Visited expression.</returns>
            protected override Expression Visit(Expression exp)
            {
                if (exp == null)
                {
                    return null;
                }

                if (this.candidates.Contains(exp))
                {
                    return this.Evaluate(exp);
                }

                return base.Visit(exp);
            }

            /// <summary>
            /// Evaluates an expression.
            /// </summary>
            /// <param name="e">Expression to evaluate.</param>
            /// <returns>Resulting expression.</returns>
            private Expression Evaluate(Expression e)
            {
                if (e.NodeType == ExpressionType.Constant)
                {
                    return e;
                }

                LambdaExpression lambda = Expression.Lambda(e);
                Delegate fn = lambda.Compile();
                return Expression.Constant(fn.DynamicInvoke(null), e.Type);
            }
        }

        /// <summary>
        /// Performs bottom-up analysis to determine which nodes can possibly
        /// be part of an evaluated sub-tree.
        /// </summary>
        private class Nominator : ExpressionVisitor
        {
            /// <summary>
            /// Determines if nominator can be evaluated.
            /// </summary>
            private Func<Expression, bool> canBeEvaluated;

            /// <summary>
            /// Expression candidates.
            /// </summary>
            private HashSet<Expression> candidates;

            /// <summary>
            /// Determines if nominator cannot be evaluated.
            /// </summary>
            private bool cannotBeEvaluated;

            /// <summary>
            /// Initializes a new instance of the Nominator class.
            /// </summary>
            /// <param name="canBeEvaluated">Describes whether the expression can be evaluated.</param>
            internal Nominator(Func<Expression, bool> canBeEvaluated)
            {
                this.canBeEvaluated = canBeEvaluated;
            }

            /// <summary>
            /// Nominates an expression.
            /// </summary>
            /// <param name="expression">Expression to nominate.</param>
            /// <returns>Resulting hash set,</returns>
            internal HashSet<Expression> Nominate(Expression expression)
            {
                this.candidates = new HashSet<Expression>();
                this.Visit(expression);
                return this.candidates;
            }

            /// <summary>
            /// Visits an expression.
            /// </summary>
            /// <param name="expression">Expression to visit.</param>
            /// <returns>Resulting expression.</returns>
            protected override Expression Visit(Expression expression)
            {
                if (expression != null)
                {
                    bool saveCannotBeEvaluated = this.cannotBeEvaluated;
                    this.cannotBeEvaluated = false;
                    base.Visit(expression);
                    if (!this.cannotBeEvaluated)
                    {
                        if (this.canBeEvaluated(expression))
                        {
                            this.candidates.Add(expression);
                        }
                        else
                        {
                            this.cannotBeEvaluated = true;
                        }
                    }

                    this.cannotBeEvaluated |= saveCannotBeEvaluated;
                }

                return expression;
            }
        }
    }
}
